using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using FoundationLib.Ifc.Domain;
using FoundationLib.Ifc.DependencyInjection;
using System.Threading;


namespace FoundationLib.Common.Domain 
{
	/// <summary>
	/// Provides an implementation for a simple in-memory message bus
	/// </summary>
	public sealed class SimpleMessageBus 
	{
		/// <summary>
		/// A list of ad-hoc actions serving as message handlers
		/// </summary>
		private Dictionary<Type, List<Delegate>> actions;
		/// <summary>
		/// The dependency injection container
		/// </summary>
		private IContainer container;

		/// <summary>
		/// Creates a simple message bus instance
		/// </summary>
		/// <param name="container">The dependency injection container</param>
		public SimpleMessageBus(IContainer container)
		{
			if (container == null)
				throw new ArgumentNullException("container");
			this.container = container;
		}

		/// <summary>
		/// Disposes of the instance
		/// </summary>
		public void Dispose()
		{
			this.actions = null;
		}

		private void EnsureActionContainerInstantiated()
		{
			if (this.actions == null)
				this.actions = new Dictionary<Type, List<Delegate>>();
		}

		private void AddAction<T>(Action<T> action)
		{
			this.EnsureActionContainerInstantiated();
			List<Delegate> delegates;
			if (this.actions.TryGetValue(typeof(T), out delegates))
			{
				if (delegates == null)
					delegates = new List<Delegate>();
				delegates.Add(action);
			}
			else
			{
				this.actions.Add(typeof(T), new List<Delegate> { action });
			}

		}

		private IEnumerable<Action<T>> GetActions<T>()
		{
			if (this.actions == null)
				return null;
			
			List<Delegate> delegates;
			if (this.actions.TryGetValue(typeof(T), out delegates))
			{
				return delegates.OfType<Action<T>>();
			}
			return null;
		}

		/// <summary>
		/// Registers the given action as a message handler
		/// </summary>
		/// <param name="handler">The action delegate serving as a message handler</param>
		public void Subscribe<T>(Action<T> handler) where T : IMessage
		{
			if (handler == null)
				throw new ArgumentNullException("handler");
			this.AddAction(handler);
		}

		/// <summary>
		/// Registers the given message handler
		/// </summary>
		/// <param name="handler">The message handler</param>
		public void Subscribe<T>(IHandles<T> handler) where T : IMessage
		{
			if (handler == null)
				throw new ArgumentNullException("handler");
			this.AddAction(new Action<T>(handler.Handle));
		}

		/// <summary>
		/// Sends a command to the bus
		/// </summary>
		/// <param name="message">The message</param>
		public void Send<T>(T message) where T : ICommand
		{
			if (message == null)
				throw new ArgumentNullException("message");

			// Invoking event handlers resolved by dependency injection
			if (container != null)
			{
				IHandles<T> handler = container.Resolve<IHandles<T>>();
				handler.Handle(message);
			}

			// Invoking event handler actions
			IEnumerable<Action<T>> actions = this.GetActions<T>();
			if (actions != null)
			{
				Action<T> action = actions.FirstOrDefault();
				if (action != null)
					action(message);
			}
		}

		/// <summary>
		/// Removes all subscriptions made explicitly with this bus
		/// </summary>
		public void ClearSubscriptions()
		{
			if (this.actions != null)
				this.actions.Clear();
		}

		/// <summary>
		/// Publishes the given event to the bus
		/// </summary>
		/// <param name="domainEvent">The domain event published to the bus</param>
		public void Publish<T>(T domainEvent) where T : IEvent
		{
			if (domainEvent == null)
				throw new ArgumentNullException("domainEvent");

			// Invoking event handlers resolved by dependency injection
			if (this.container != null)
			{
				var handlers = this.container.ResolveAll<IHandles<T>>();
				foreach (var handler in handlers)
				{
					ThreadPool.QueueUserWorkItem(x => handler.Handle(domainEvent));
				}
			}

			// Invoking event handler actions
			IEnumerable<Action<T>> actions = this.GetActions<T>();
			if (actions != null)
			{
				foreach (var action in actions)
				{
					if (action != null)
						ThreadPool.QueueUserWorkItem(x => action(domainEvent));
				}
			}
		}

	}

}